﻿package rob.core{
	import flash.display.Sprite;
	import flash.geom.Matrix;
	import rob.data.Vert3d;
	import rob.data.Vect3d;
	import rob.data.UVCoord;
	import rob.data.BBox;
	import rob.maths.*;
	import rob.util.NumberUtil;

	public class Mesh3d extends Primitive3d{
		public var name:String;
		// vertices of mesh
		private var _aVertex:Array
		// UV coordinates for texturing
		private var _aUVCoord:Array;
		// polygons: all triangles
		private var _aPolygon:Array;
		// original form of faces (from parser) is as array of array of vertex ids
		// e.g. [[0,1,2],[1,2,3],..]
		private var _orig_faces:Array;
		// edge polys and vert polys are intermediate vars used to compute
		// -- edge weights, because we don't want to draw line around triangles
		// -- vert weights, used for Gouraud shading, we compute average normal at vertex
		private var _edgePolys:Array, _vertPolys:Array;
		// * baseVertexIds is ordered (by connection) list of vertex ids on base
		// * baseVertex are the vectors instead of ids, only defined inside a clone
		//   (because need to define new vertices for each clone, and the base is in terms
		//   of those new vertices, rather than the 'master' vertices)
		private var _aBaseVertex:Array, _aBaseVertexIds:Array;
		// TEST: contains a poly for each base edge, idea is to order bot sprites via 
		// base edges and then put above/below this polygons order (problems will arise tho...)
		private var _aBasePolyIds:Array, _aBasePolygon:Array;
		
		public function Mesh3d(meshName:String, verts:Array, uvs:Array, faces:Array, cloning=false):void{
			// name and vertices
			name = meshName;
			_aVertex = verts;
			_aUVCoord = uvs;
			// store orig face ids + setup bounding box
			_orig_faces = faces;
			_bbox = new BBox();
			// center and put mesh on floor
			if(!cloning){
				updateBBox();
				centerByMesh();
				putOnFloor();
			}
			// use _aVertex, _aUVCoord and faces to construct _aPolygon
			// also if cloning is false then compute edge weights (so don't draw triangles)
			_aPolygon = [];
			_edgePolys = [];
			_vertPolys = [];
			
			// note: cloning means we don't re-evaluate edgePolys/vertPolys/computeBaseVerts 
			// every time we copy the mesh (that would be a waste)
			if(!cloning){
				_initWeighting();
			}
			var fVerts:Array, fUVCoords:Array, normal:Vect3d, polyId:uint;
			for each(var f:Object in faces){
				// construct polygon
				fVerts = f.face.map(function(i){return _aVertex[i]});
				fUVCoords = f.uvface.map(function(i){return _aUVCoord[i]});
				_aPolygon.push(new Poly3d(f.mat,fVerts,fUVCoords));
				// iteratively construct edgePolys, vertPolys
				if(!cloning){
					polyId = _aPolygon.length - 1;
					_addToEdgePolys(f.face,polyId);
					_addToVertPolys(f.face,polyId);
				}
			}
			// now use _edgePolys to decide weight of lines
			// and _vertPolys to decide weight of vertices (for Gouraud)
			if(!cloning){
				_computeLineWeights(faces);
				_computeVertWeights(faces);
				_computeBaseVerts();
				_computeBasePolys();
				_edgePolys = null;
				_vertPolys = null;
			}
		}
		
		// TRANSFORM
			// assume that mesh is centered for scale
			public function scale(s:Number):void{
				for each(var p:Vert3d in _aVertex) VertexMaths.scaleTo(s,p);
			}
			
			public function scaleVect(sv:Vect3d){
				for each(var p:Vert3d in _aVertex) VertexMaths.scaleVectTo(sv,p);
			}
			
			// project points relative to a camera
			public function project(c:Camera3d):void{
				for each(var p:Vert3d in _aVertex){
					VertexMaths.project(p,c);
				}
			}
			
			// center mesh at origin (via base center)
			public function centerByBase():void{
				var avg:Vert3d = VertexMaths.average(_aBaseVertex);
				for each(var p:Vert3d in _aVertex) VertexMaths.subTo(p,avg);
				position.set(0,0,0);
			}
			
			public function centerByMesh():void{
				//var avg:Vert3d = VertexMaths.average(_aVertex);a
				var avg:Vect3d = _bbox.center;
				for each(var p:Vert3d in _aVertex) VertexMaths.subTo(p,avg);
				position.set(0,0,0);
			}
			
			// angle in radians, rotate about (0,1,0)
			public function rotateBy(angle:Number):void{
				var tx:Number, tz:Number;
				for each(var p:Vert3d in _aVertex){
					tx = p.x;	tz = p.z;
					p.x = Math.cos(angle) * tx - Math.sin(angle) * tz;
					p.z = Math.sin(angle) * tx + Math.cos(angle) * tz;
				}
			}
	
			public function moveTo(v:Vert3d):void{
				var rel:Vect3d = VertexMaths.sub(v,position);
				for each(var p:Vert3d in _aVertex) VertexMaths.addTo(p,rel);
				position.setV(v);
			}
			
			public function putOnFloor():void{
				updateBBox();
				var dy:Vect3d = new Vect3d(0,-_bbox.min.y,0);
				for each(var p:Vert3d in _aVertex) VertexMaths.addTo(p,dy);
			}
			
			// apply a 2x2 matrix to x,z components
			public function transform(m:Matrix):void{
				var px:Number, py:Number;
				for each(var p:Vert3d in _aVertex){
					px= p.x;	 pz= p.z;
					p.x = m.a * px + m.c * pz;
					p.z = m.b * px + m.d * pz;
				}
				// if we flipped the mesh then reverse all vertex lists in
				// polygons: that way they remain anti-clockwise order
				if(m.a*m.d < 0 || m.b*m.c > 0){
					for each(var f:Poly3d in _aPolygon){
						f.aVertex.reverse();
					}
				}
			}
		
		// WEIGHTING (edge & vertex)
			private function _initWeighting():void{
				for(var i:uint = 0; i < _aVertex.length; i++){
					_edgePolys[i] = [];
					_vertPolys[i] = [];
				}
			}
		
		// EDGE WEIGHTING
			// run this for each poly as we create it...
			private function _addToEdgePolys(f:Array,polyId:Number):void{
				var minId:Number, maxId:Number;
				// add face id (assume triangle) to each edge
				for(var i:uint = 0; i < 3; i++){
					minId = Math.min(f[i],f[(i+1)%3]);
					maxId = Math.max(f[i],f[(i+1)%3]);
					if(!(_edgePolys[minId][maxId] is Array)) _edgePolys[minId][maxId] = [];  
					_edgePolys[minId][maxId].push(polyId);
				}
			}
			// use _edgeWeights to compute line weights
			private function _computeLineWeights(faces:Array):void{
				var lws:Array, minId:Number, maxId:Number;
				// for every edge in every poly find face parents, average normals and convert to weight
				for(i in _aPolygon){
					lws = [];
					for(var j:uint = 0; j < 3; j++){
						minId = Math.min(faces[i].face[j],faces[i].face[(j+1)%3]);
						maxId = Math.max(faces[i].face[j],faces[i].face[(j+1)%3]);
						// if triangles then exactly two edges
						// however might be floor piece (other poly isn't there)
						pIds = _edgePolys[minId][maxId];
						if(pIds.length != 2) lws.push(1);
						else{
							dp = VectorMaths.dp(_aPolygon[pIds[0]].normal,_aPolygon[pIds[1]].normal);
							if(dp < .999) lws.push(1);
							else lws.push(0);
						}
					}
					// finally set line weights
					_aPolygon[i].setLineWeights(lws);
					//trace(lws);
				}
			}
		
		// VERTEX WEIGHTING
			private function _addToVertPolys(f:Array,polyId:Number):void{
				for each(var vertId:uint in f){
					_vertPolys[vertId].push(polyId);
				}
			}
			
			private function _computeVertWeights(faces:Array):void{
				var vws:Array, i:uint, a:Array, vertWeights:Array;
				// first map _vertPolys into average normals
					for (i in _vertPolys)
						_vertPolys[i] = _vertPolys[i].map(function(i){return _aPolygon[i].normal;});
					_vertPolys = _vertPolys.map(function (a){return VectorMaths.average(a);});
				// then map normals to shades of gray (0 -> 255)
				// note we assume light is directly down
					vertWeights = _vertPolys.map(
							function (n){
								var kup:Number = (n.y >= 0) ? n.y : 0;
								//var k:Number = 80*kup + 60*Math.abs(n.x + n.z) + 30*Math.abs(n.x - n.z)+ 20;
								var k:Number = 200 * n.y + 100;
								return NumberUtil.constrain(k,0,255);});
				// now set 'shade' on each vertex
					for (i in _aVertex) _aVertex[i].shade = vertWeights[i];
				// no need to actually setup skin yet
				vertWeights = null;
			}
		
		// BASE COMPUTATION		
			// find the base of building for collision detection etc.
			// to find outside edges consider triangles which have 2 points on ground & aren't flat
			// NOTE: this doesn't work for flat stuff (e.g. ground plane) only for buildings because
			// it looks for the walls to find the base... also possible to find base without this
			private function _computeBaseVerts():void{
				var fIds:Array = [], bEdges:Array = [], p:Poly3d, vs:Array;
				var i:uint;
				// collect baseEdges
				for(i in _aPolygon){
					p = _aPolygon[i];
					// if face is not in plane y = const.
					if(! NumberUtil.nearZero(1 - Math.abs(p.normal.y)) ){
						vs = p.aVertex;
						// use original face ids e.g. [[1,2,3],[2,3,4],...]
						fIds = _orig_faces[i].face;
						// check if 2 verts have .y ~= 0
						if(NumberUtil.nearZero(vs[0].y)){
							if(NumberUtil.nearZero(vs[1].y)) bEdges.push([fIds[0],fIds[1]]);
							else if(NumberUtil.nearZero(vs[2].y)) bEdges.push([fIds[2],fIds[0]])
						}
						else if(NumberUtil.nearZero(vs[1].y) && NumberUtil.nearZero(vs[2].y)){
							bEdges.push([fIds[1],fIds[2]])
						}
					}
				}
				// now order them so have array of vertices s.t 
				// (forall i) v_i -> v_i+1 forms base edge
				// do this by starting [v0,v1] then look for edge of form [v1,_] etc.
				var baseEdges:Array = [], matchEdge:Array, matchVert:uint, matched:Boolean;
				var l:int = bEdges.length - 1;

				matchEdge = bEdges.pop();
				while(l-- > 0){
					baseEdges.push(matchEdge[0]);
					matchVert = matchEdge[1];
					matched = false;
					for(i in bEdges){
						if(bEdges[i][0]==matchVert || bEdges[i][1]==matchVert){
							matchEdge = bEdges[i];
							if(bEdges[i][1]==matchVert) matchEdge = matchEdge.reverse();
							bEdges.splice(i,1);	
							matched = true;
							// note: we don't break here because possible to have triangles
							// with only 2 different vertices (so bEdges can contain 'duplicates')
						}
					}
					if(!matched) throw new Error("Base edge matching error in Mesh3d._computeBase");
				}
				if(baseEdges.length > 0) baseEdges.push(matchEdge[0]);
				// get final list of base vertices
				_aBaseVertexIds = baseEdges;
			//trace(_aBaseVertexIds);
				baseEdges = null;
			}
		
			// TEST: for each base edge find a polygon (not on base) which touches
			private function _computeBasePolys():void{
				var vIds:Array = _aBaseVertexIds;
				var l = vIds.length, minId:uint, maxId:uint, polyIds:Array;
				var polyId:uint, poly:Poly3d;
				_aBasePolyIds = [];
				for(var i in vIds){
					//trace(vIds[i] + "," + vIds[(i+1)%l]);
					minId = Math.min(vIds[i],vIds[(i+1)%l]);
					maxId = Math.max(vIds[i],vIds[(i+1)%l]);
					polyIds = _edgePolys[minId][maxId];
					// find poly in polyIds which isn't on base
					poly = _aPolygon[polyIds[0]];
					polyId = (NumberUtil.nearZero(Math.abs(poly.normal.y)-1)) ? polyIds[1] : polyIds[0];
					_aBasePolyIds[i] = polyId;
				}
				//trace(_aBasePolyIds);
			}
		
		
		public function updateBBox():void{
			_bbox.compute(_aVertex);
		}
		public function updateNormals():void{
			for each(var p:Poly3d in _aPolygon) p.updateNormal();
		}
		
		
		// clone a mesh, we only display clones
		public function clone():Mesh3d{
			// duplicate vertices and uvpoints
			var verts:Array = [], uvs:Array = [];
			for each(var v:Vert3d in _aVertex) verts.push(v.clone());
			for each(var u:UVCoord in _aUVCoord) uvs.push(u.clone());
			// final bool tells us not to recalc edgeweights etc.
			var m:Mesh3d =  new Mesh3d(name,verts,uvs,_orig_faces,true);
			// set edgeweights
			var i:uint;
			for(i in _aPolygon){
				m.aPolygon[i].setLineWeights(_aPolygon[i].lineWeights);
			}
			// set vertex shading
			for(i in _aVertex) m.aVertex[i].shade = _aVertex[i].shade;
			for each(var p:Poly3d in m.aPolygon) if(p.gouraud) p.setupSkin();
			// set base vertex array
			m.aBaseVertex = _aBaseVertexIds.map(function(i){return m.aVertex[i];});
			// set base polys array
			m.aBasePolygon = _aBasePolyIds.map(function(i){return m.aPolygon[i];});
			
			//m.centerByMesh();
			return m;
		}
		
		public function set aBaseVertex(a:Array):void{ _aBaseVertex = a;}
		public function set aBasePolygon(a:Array):void{ _aBasePolygon = a;}
		
		public function get bbox():BBox{ return _bbox;}
		public function get aVertex():Array{ return _aVertex;}
		public function get aBaseVertex():Array{ return _aBaseVertex;}
		public function get aBasePolygon():Array{ return _aBasePolygon;}
		public function get aPolygon():Array{ return _aPolygon;}
		public function get aNormals():Array{ return _aNormals;}

		
		//public function set container(c:Sprite):void{_container = c;}
	}
}